Introduzione

Anzitutto bisogna premettere che l’UnrealScript Φ stato introdotto quale ibrido fra C/C++ e Java al fine di combinare le caratteristiche di entrambi ed offrire un linguaggio duttile. Questo documento Φ riservato a chi ha giα una buona conoscenza di uno di questi linguaggi di programmazione. La guida non offre molti esempi in quanto il modo migliore di comprendere l’UnrealScript Φ di visionare direttamente il codice del gioco all’interno di UnrealEd.

Esempio

Questo esempio illustra una tipica e semplice classe di UnrealScript, mettendo in luce la sintassi e le caratteristiche.

//=============================================================================
// TriggerLight.
// Una sorgente di luce che pu≥ essere accesa e spenta via trigger
//=============================================================================
class TriggerLight expands Light;
 
//-----------------------------------------------------------------------------
// Variabili
 
var() float ChangeTime; // Tempo di passaggio della luce da accesa a spenta
var() bool bInitiallyOn; // Se Φ inizialmente accesa
var() bool bDelayFullOn; // ritarda e poi si accende completamente
 
var ELightType InitialType; // Tipo iniziale della luce
var float InitialBrightness; // Luminositα iniziale della luce
var float Alpha, Direction;
var actor Trigger;
 
//-----------------------------------------------------------------------------
// Funzioni
 
// Funzione chiamata allÆinizio del gioco
function BeginPlay()
{
	Disable( 'Tick' );
	InitialType = LightType;
	InitialBrightness = LightBrightness;
	if( bInitiallyOn )
	{
		Alpha = 1.0;
		Direction = 1.0;
	}
	else
	{
		LightType = LT_None;
		Alpha = 0.0;
		Direction = -1.0;
	}
}
 
// Chiamata mentre il tempo passa
function Tick( float DeltaTime )
{
	LightType = InitialType;
	Alpha += Direction * DeltaTime / ChangeTime;
	if( Alpha > 1.0 )
	{
		Alpha = 1.0;
		Disable( 'Tick' );
		if( Trigger != None )
			Trigger.ResetTrigger();
	}
	else if( Alpha < 0.0 )
	{
		Alpha = 0.0;
		Disable( 'Tick' );
		LightType = LT_None;
		if( Trigger != None )
			Trigger.ResetTrigger();
	}
	if( !bDelayFullOn )
		LightBrightness = Alpha * InitialBrightness;
	else if( (Direction>0 && Alpha!=1) || Alpha==0 )
		LightBrightness = 0;
	else
		LightBrightness = InitialBrightness;
}
 
//-----------------------------------------------------------------------------
// Stati ( public )
 
// Il trigger accende la luce
state() TriggerTurnsOn
{
	function Trigger( actor Other, pawn EventInstigator )
	{
		Trigger = None;
		Direction = 1.0;
		Enable( 'Tick' );
	}
}
 
// Il trigger spegne la luce
state() TriggerTurnsOff
{
	function Trigger( actor Other, pawn EventInstigator )
	{
		Trigger = None;
		Direction = -1.0;
		Enable( 'Tick' );
	}
}
 
// Il trigger inverte lo stato della luce 
state() TriggerToggle
{
	function Trigger( actor Other, pawn EventInstigator )
	{
		log("Toggle");
		Trigger = Other;
		Direction *= -1;
		Enable( 'Tick' );
	}
}
 
// Il trigger controlla la luce
state() TriggerControl
{
	function Trigger( actor Other, pawn EventInstigator )
	{
		Trigger = Other;
		if( bInitiallyOn ) Direction = -1.0;
		else Direction = 1.0;
		Enable( 'Tick' );
	}
	function UnTrigger( actor Other, pawn EventInstigator )
	{
		Trigger = Other;
		if( bInitiallyOn ) Direction = 1.0;
		else Direction = -1.0;
		Enable( 'Tick' );
	}
}

Gli elementi chiave da prendere in considerazione in questo script sono:

La dichiarazione delle classi. Ogni classe "expands" ( deriva da ) una classe genitrice, ed ogni classe appartiene ad un "pacchetto", ossia ad un insieme di oggetti connessi. Tutte le funzioni e le variabili appartengono ad una classe e sono accessibili solamente da un actor appartenente a quella classe. Non esistono funzioni o variabili globali.

La dichiarazione delle variabili. UnrealScript supporta un vario set di tipi di variabili che include la maggior parte dei tipi del C/Java, riferimenti a oggetti, strutture e array. In aggiunta, le variabili possono essere create in proprietα modificabili, a cui i designer possono accedere in UnrealEd senza alcuna programmazione.

Le funzioni. Esse possono accettare una lista di parametri ( argomenti ), e possono ritornare un valore. Le funzioni possono avere variabili locali. Alcune funzioni sono chiamate dall’engine di Unreal Tournament stesso ( come BeginPlay ), mentre alcune possono essere chiamate da un’altra porzione di script ( come Trigger ).

Il codice. Tutte le parole chiave standard ANSI C e Java sono supportate, come "for", "while", "break", "switch", "if", e cos∞ via. Le parentesi ( tonde, quadre e graffe ) sono utilizzate in UnrealScript come in C/C++ e Java.

Riferimenti a oggetti e actors. Si possono notare svariati casi in cui una funzione Φ chiamata all’interno di un altro oggetto usando un riferimento ad una classe.

La parola chiave "state". Questo script definisce vari "stati", che sono gruppi di funzioni, variabili e codice che sono eseguiti solo quando l’actor si trova in quel determinato stato.

Notare che tutte le parole chiave, i nomi delle variabili, le funzioni, i nomi degli oggetti sono case-insensitive. Infatti per UnrealScript Φ la stessa cosa scrivere "Demon", "demON" e "demon".

L’Unreal Virtual Machine

L’Unreal Virtual Machine consiste di vari componenti: il server, il client, il rendering engine e il codice di supporto dell’engine. L’Unreal server controlla tutto il gioco e l’interazione tra i giocatori e gli actors. In un single-player, sia l’Unreal client che l’Unreal server vengono avviati sulla stessa macchina; in una partita su Internet vi Φ invece un server dedicato che opera su una macchina; tutti i giocatori si connettono a questa macchina e sono clients. Il gioco Φ ambientato all’interno di un "livello", un ambiente chiuso che contiene la geometria e gli actors. Anche se UnrealServer pu≥ eseguire pi∙ di un livello simultaneamente, ogni livello opera indipendentemente: gli actors non possono spostarsi fra i livelli nΘ comunicare con actors di altri livelli. Ogni actor in una mappa pu≥ sia essere sotto il controllo del giocatore ( ci possono essere pi∙ giocatori in una partita in rete ) o sotto il controllo dello script. Nel secondo caso, il suo script definisce completamente come l’actor si muove ed interagisce con gli altri actors. Con tutti gli actors attivi, gli script in esecuzione e gli eventi che avvengono nel livello, ci si chiederα come una persona pu≥ comprendere il flusso di esecuzioni in un UnrealScript. La risposta Φ la seguente:

Per gestire il tempo, Unreal Tournament divide ogni secondo di gioco in "Ticks". Un tick Φ la pi∙ piccola unitα di tempo nella quale tutti gli actors in un livello vengono aggiornati. Un tick tipicamente prende fra il centesimo ed il decimo di secondo: il tempo del tick Φ ovviamente dipendente dalla potenza della macchina su cui si esegue UT ( pi∙ veloce Φ, minore Φ la durata del tick ). Alcuni comandi in UnrealScript richiedono zero ticks per essere eseguiti ( cioΦ vengono eseguiti senza il passaggio del tempo ) mentre altri richiedono alcuni ticks. Funzioni che richiedono il passaggio del tempo sono chiamate "funzioni latenti". Alcuni esempi di funzioni latenti includono "Sleep", "FinishAnim" e "MoveTo". Le funzioni latenti in UnrealScript possono essere chiamate dal codice all’interno di uno stato e non da codice all’interno di una funzione. Mentre un actor sta eseguendo una funzione latente, l’esecuzione di stato per quell’actor non continua finchΦ la funzione latente viene completata. Comunque gli altri actors, o la Virtual Machine, possono chiamare funzioni dell’actor. Il risultato Φ che tutte le funzioni dell’UnrealScript possono essere chiamate in ogni momento, anche quando le funzioni latenti sono in attesa. In termini tradizionali della programmazione, UnrealScript agisce come se ogni actor in un livello abbia il suo thread di esecuzione. Internamente, UT non usa i threads di Windows poichΘ sarebbe inefficiente ( Windows 95/NT non gestisce migliaia di threads simultanei in maniera efficiente ). Invece UnrealScript simula i threads: questo fatto Φ trasparente per il codice UnrealScript, per≥ diventa evidente quando si scrive codice C++ che interagisce con UnrealScript. Tutti gli UnrealScripts vengono eseguiti in parallelo. Se ci sono 100 mostri che camminano in un livello, tutti e 100 gli scripts di questi mostri vengono eseguiti indipendentemente e in parallelo.

Le classi

Prima di iniziare a lavorare con l’UnrealScript Φ importante capire le relazioni di alto livello degli oggetti in Unreal. L’architettura di UT Φ molto differente da quella della maggior parte dei giochi: UT Φ puramente orientato agli oggetti ( come gli oggetti ActiveX/COM ), il che comporta il supporto a concetti di alto livello come la serializzazione ed il polimorfismo. C’Φ un maggiore beneficio nel modello orientato agli oggetti di Unreal: altre nuove funzionalitα e tipi di oggetti possono essere aggiunti al gioco mediante la creazione di sottoclassi invece che modificare blocchi di codice esistente. Questa forma di estensibilitα Φ estremamente potente in quanto permette di creare migliorie e modifiche ad UT col minimo sforzo.

Object Φ la classe genitrice di tutti gli oggetti in Unreal. Tutte le funzioni nella classe Object perci≥ sono accessibili ovunque dato che ogni cosa deriva da Object. Object Φ una classe astratta di base, cioΦ non esegue specificamente nulla di utile. Tutte le funzionalitα sono fornite dalle sottoclassi, come Texture ( una texture map ) o Actor, che Φ la classe genitrice per tutti gli oggetti utilizzati in Unreal. La classe Actor contiene tutte le funzionalitα necessarie ad un actor per muoversi ed interagire con altri actor, oppure per altri aspetti del gioco.

Pawn ( expands Actor ) Φ la classe genitrice di tutte le creature e giocatori di UT capaci di AI di alto livello.

Class ( expands Object) Φ un tipo speciale di oggetto che descrive una di oggetti. Ci≥ pu≥ sembrare confuso: una classe Φ un oggetto, ed una classe descrive certi oggetti. Ci sono per≥ molti casi in cui ci si imbatte in oggetti Class. Per esempio, quando si genera un nuovo actor in UnrealScript, si pi≥ specificare la classe del nuovo actor con un oggetto Class.

Con UnrealScript si pu≥ scrivere codice per ogni classe Object ma le classi veramente utili sono quelle derivate da Actor.

La dichiarazione di classi

Ogni script corrisponde ad una classe sola; lo script inizia con la dichiarazione della classe, la genitrice e ogni informazione aggiuntiva utile per la classe. La forma canonica Φ:

class MyClass expands MyParentClass;

Qui si dichiara una nuova classe chiamata "MyClass" che eredita le funzionalitα di "MyParentClass", la classe genitrice.

Ogni classe eredita tutte le variabili, funzioni e stati dalla classe genitrice. Essa poi pu≥ dichiarare nuove variabili, aggiungere nuove funzioni ( o ridefinire funzioni esistenti ), aggiungere nuovi stati ( o aggiungere funzionalitα agli stati esistenti ). Il tipico approccio al design delle classi in UnrealScript Φ creare una nuova classe ( per esempio un mostro Minotauro ) che derivi da una classe esistente che abbia le funzionalitα richieste ( in questo caso la classe Pawn, la base per tutti i mostri ). Con questo sistema non bisogna riscrivere tutto il codice bens∞ personalizzare le funzionalitα giα presenti o aggiungerne di nuove se necessario. Ci≥ Φ estremamente potente per implementare AI in Unreal, dove il sistema di intelligenza artificiale integrato fornisce un enorme quantitativo di funzionalitα di base da poter utilizzare nella creazione della propria creatura. La dichiarazione di una classe pu≥ avere diversi specificatori opzionali che agiscono sulla classe stessa:

Intrinsic: significa "questa classe utilizza dietro le quinte il supporto C++". UT si aspetta che le classi intrinsic contengano una implementazione C++ nella DLL corrispondente al pacchetto della classe. Per esempio, se il pacchetto si chiama "Robots", UT cerca in "Robots.dll" l’implementazione della classe intrinsic che Φ generata dalla macro C++ IMPLEMENT_CLASS.

HideParent: significa "quando un utente sta modificando un oggetto di questa classe in UnrealEd, non mostrare nell’editor alcuna delle proprietα della classe genitrice".

Abstract: dichiara la classe come "classe astratta di base". Questo evita che l’utente aggiunga actors di questa classe nel livello in UnrealEd, in quanto ci≥ non avrebbe significato. Per esempio la classe di base Pawn Φ astratta, mentre la sottoclasse Brute non lo Φ: Φ possibile infatti aggiungere un Brute nel livello, ma non un Pawn generico.

Guid: associa alla classe un identificativo univoco globale ( un numero a 128 bit ). Tale Guid non Φ attualmente utilizzato, ma diventerα utile quando il supporto nativo COM verrα in seguito aggiunto ad Unreal.

Transient: significa "gli oggetti appartenenti a questa classe non dovrebbero mai essere salvati su disco". Utile solo in combinazione con certi tipi di classi intrinsic che non sono persistenti normalmente, come i giocatori e le finestre.

ScriptConst: significa "gli oggetti di questa classe devono essere dichiarati Const in UnrealScript", per evitare un cambiamento del loro valore durante il gioco.

Variabili

Variabili semplici

Alcuni esempi di dichiarazioni di variabili in UnrealScript:

var int a; // Dichiara un intero di nome "a".
var byte Table[64]; // Dichiara una stringa di 64 byte di nome "Table".
var string[32] PlayerName; // Dichiara una stringa di massimo 32 caratteri.
var actor Other; // Dichiara una variabile riferita ad un actor.

Le variabili possono apparire in due tipi di posti in UnrealScript: le variabili di istanza, che valgono per un intero oggetto, compaiono subito dopo la dichiarazione della classe. Le variabili locali invece appaiono all’interno di una funzione e sono attive solo mentre la funzione viene eseguita. Le variabili di istanza vengono dichiarate con la parola chiave "var". Le variabili locali sono dichiarate con la parola chiave "local". 

Questi sono i tipi di variabili supportati in UnrealScript:

byte: un valore a 8-bit con un range che va da 0 a 255.

int: un valore intero a 32-bit.

bool: un valore booleano: "vero" o "falso".

float: un valore in virgola mobile a 32-bit.

string: una stringa di caratteri.

name: il nome di qualcosa in UT ( come il nome di una funzione, stato, classe, ecc. ). I nome sono memorizzati come 16-bit nella tabella globale dei nomi. I nomi corrispondono a semplici stringhe da 1 a 31 caratteri. I nomi per≥ non sono come le stringhe: queste ultime possono essere modificate dinamicamente, mentre i nomi hanno valore predefinito.

Enumerazioni: una variabile che pu≥ assumere solo uno di una serie di valori predefiniti. Per esempio l’enumerazione ELightType definita nello script Actor descrive una luce dinamica e prende valori come LT_None, LT_Pulse, LT_Strobe, e cos∞ via.

Riferimenti ad un oggetto o ad un actor: una variabile che si riferisce ad un altro oggetto o actor nel livello. Per esempio la classe Pawn ha un riferimento actor "Enemy" che specifica quale actor il pawn deve attaccare. I riferimenti ad oggetti o actors sono strumenti molto potenti poichΘ consentono di accedere a variabili e funzioni di un altro actor. Per esempio nello script Pawn si pu≥ scrivere "Enemy.Damage(123)" per richiamare la funzione Damage del nemico, col risultato di infliggere danno a quest’ultimo. I riferimenti ad oggetti possono anche contenere un valore speciale chiamato "None", l’equivalente del puntatore del C "NULL": significa che la variabile non si riferisce ad alcun oggetto.

Strutture: simili alle strutture del C, quelle di UnrealScript permettono di creare nuovi tipi di variabili che contengano sotto-variabili. Per esempio, due strutture comuni sono i "vector", che consistono in tre componenti X,Y e Z; oppure i "rotator", che consistono in pitch, yaw e roll, ossia le rotazioni su ognuno dei tre assi dello spazio.

Le variabili possono inoltre contenere specificatori aggiuntivi :

const: tratta il contenuto della variabile come costante. In UnrealScript in questo caso si pu≥ leggere il valore della variabile const ma non si pu≥ scriverlo nuovamente. "Const" Φ utilizzato solo per variabili di cui l’engine Φ responsabile per l’aggiornamento e che non verrebbero correttamente aggiornate con l’UnrealScript, come per esempio la Location di un actor ( che pu≥ essere modificata solo chiamando la funzione MoveActor ).

input: rende accessibile la variabile al sistema di input di Unreal, cosicchΘ quell’input ( quale un tasto premuto o un movimento del joystick ) pu≥ essere direttamente mappato su di esso. Valido solo con variabili si tipi "byte" e "float".

transient: dichiara che la variabile Φ presente per un uso temporaneo e non Φ parte della persistenza dell’oggetto. Le variabili transient non vengono salvate su disco; esse sono inizializzate a zero quando l’actor viene caricato.

intrinsic: dichiara che la variabile viene caricata e salvata da codice C++ e non da UnrealScript.

private: la variabile Φ privata e vi si pu≥ accedere solo tramite lo script della classe; nessun’altra classe ( incluse le sottoclassi) pu≥ accedervi.

Gli array sono dichiarati attraverso la seguente sintassi:

var int MyArray[20]; // Dichiara un array di 20 interi.

UnrealScript supporta solo array monodimensionali, anche se Φ possibile simulare array multidimensionali creando personalmente la struttura a riga/colonna. In UnrealScript Φ possibile rendere "modificabile" una variabile di istanza cosicchΦ gli utenti possano modificarne il valore in UnrealEd. Questo meccanismo Φ responsabile per l’intero contenuto della finestra di dialogo "Actor Properties" di UnrealEd: ogni cosa si veda Φ semplicemente una variabile dell’UnrealScript dichiarata modificabile.

La sintassi per dichiarare una variabile modificabile Φ questa:

var() int MyInteger; // Dichiara un intero modificabile nella categoria di default. 
var(MyCategory) bool MyBool; // Dichiara un valore booleano in "MyCategory".

Variabili di riferimento ad oggetti o actors. E’ possibile dichiarare una variabile che si riferisce ad un actor o ad un oggetto:

var actor A; // Riferimento ad un actor.
var pawn P; // Riferimento ad un actor nella classe Pawn.
var texture T; // Riferimento ad un oggetto texture.

La variabile "P" sopra Φ un riferimento ad un actor nella classe Pawn. Una tale variabile pu≥ riferirsi a qualsiasi actor appartenente ad una sottoclasse di Pawn. Per esempio, P pu≥ riferirsi a un Brute, o uno Skaarj, o un Manta. Pu≥ essere qualsiasi Pawn. In ogni caso P non pu≥ riferirsi ad esempio ad un actor Trigger poichΘ non Φ una sottoclasse di Pawn. Un esempio di dove pu≥ essere utile avere una variabile di riferimento ad un actor Φ la variabile Enemy nella classe Pawn, che si riferisce all’actor che il Pawn deve attaccare. Quando si ha una variabile che si riferisce ad un actor, Φ possibile accedere alle variabili dell’actor e chiamare le sue funzioni:

// Dichiara due variabili di riferimento ad un pawn.
var pawn P, Q;
 
// Questa Φ una funzione che fa uso di P.
// Mostra alcune informazioni su P.
function MyFunction()
{
	// Imposta il nemico di P a Q
	P.Enemy = Q;
 
	// Dice a P di eseguire la sua animazione di corsa.
        P.PlayRunning();
}

Variabili che si riferiscono ad actors si riferiscono sempre ad un actor valido ( qualsiasi actor che Φ presente nel livello ) oppure contengono un valore None". None Φ equivalente al puntatore "NULL" del C/C++. Comunque in UnrealScript Φ sicuro accedere a variabili e chiamare funzioni con un riferimento "None" in quanto il risultato Φ sempre zero. Notare che un riferimento ad oggetto o actor "punta ad" un altro actor o oggetti, quindi non contiene un actor o oggetto. L’equivalente C di un riferimento ad un actor Φ un puntatore ad un oggetto nella classe AActor ( in C avremmo AActor* ). Per esempio si potrebbe avere due mostri nel livello, Bob e Fred, che stanno lottando fra loro. La variabile "Enemy" di Bob punterebbe a Fred mentre la variabile "Enemy" di Fred punterebbe a Bob. A differenza dei puntatori del C, i riferimenti ad oggetti dell’UnrealScript sono sempre sicuri ed infallibili. E’ impossibile per un riferimento ad un oggetto riferirsi ad un oggetto che non esiste o Φ invalido. Infatti in UnrealScript quando un actor o oggetto viene distrutto, tutti i riferimenti ad esso vengono automaticamente impostati a "None".

Variabili di riferimento a classi

In UT le classi sono oggetti quanto gli actors, textures e suoni. Gli oggetti di una classe appartengono alla classe chiamata "class". Ci sono spesso casi in cui si vuole memorizzare un riferimento ad un oggetto di una classe in modo tale da poter generare un actor appartenente alla classe ( senza conoscere quale Φ la classe al momento della compilazione ). Per esempio:

var() class C;
var actor A;
A = Spawn( C ); // Genera un actor appartenente ad una qualche classe "C" arbitraria.

Adesso bisogna essere sicuri di non confondere il ruolo della classe C con un oggetto appartenente alla classe C. Per fare una analogia, una classe Φ come un macinapepe e l’oggetto Φ come il pepe. Si pu≥ usare il macinapepe ( la classe ) per creare pepe ( l’oggetto appartenente alla classe ) ruotando la manovella ( chiamando la funzione Spawn )… per≥ il macinapepe non Φ pepe e non si pu≥ mangiarlo ! Quando si dichiarano variabili che si riferiscono ad oggetti della classe, si pu≥ opzionalmente usare la sintassi class <classlimitor> per limitare la variabile a contenere solo riferimenti a classi che derivano ( expand ) da una certa classe genitrice. Per esempio:

var class<actor> ActorClass;

La variabile ActorClass pu≥ riferirsi solo ad una classe che derivi dalla classe "actor". Questo Φ utile per migliorare il controllo di tipo durante la compilazione. Per esempio la funzione Spawn accetta una classe come argomento, ma il risultato ha senso solo quando la classe fornita Φ sottoclasse di Actor, e la sintassi class<classlimitor> costringe il compilatore a richiedere ci≥. Come con il lancio dinamico degli oggetti, si pu≥ lanciare dinamicamente le classi con questa sintassi:

class<actor>( SomeFunctionCall() )

Enumerazioni

Le enumerazioni sono presenti nell’UnrealScript quali conveniente modo di dichiarare variabili che possono contenere "una di" una serie di parole chiave. Per esempio, la classe actor contiene l’enumerazione EPhysics che descrive la fisicitα che UT dovrebbe applicare all’actor. Questa pu≥ essere impostata a uno dei valori predefiniti quali PHYS_None, PHYS_Walking, PHYS_Falling, ecc. Internamente le enumerazioni sono memorizzate come variabili byte. Nel design dell’UnrealScript le enumerazioni non sono necessarie per≥ rendono il codice molto pi∙ semplice da leggere: per esempio impostare la fisicitα a "PHYS_Swimming" piuttosto che "3". Ecco un codice di esempio:

// Dichiara lÆenumerazione EColor, con tre valori.
enum EColor
{
	CO_Red,
	CO_Green,
	CO_Blue
};
 
// Adesso, dichiara due variabili di tipo EColor.
var EColor ShirtColor, HatColor;
 
// Alternativamente si possono dichiarare variabili ed enumerazioni assieme.
var enum EFruit
{
	FRUIT_Apple,
	FRUIT_Orange,
	FRUIT_Bannana
} FirstFruit, SecondFruit;

Nel codice sorgente di UT vengono dichiarati valori di enumerazione come LT_Steady, PHYS_Falling e cos∞ via, piuttosto che semplicemente "Steady" o "Falling": questa Φ semplicemente questione di stile di programmazione e non Φ richiesto dal linguaggio. UnrealScript riconosce identificativi ( tags ) senza qualifica dell’enumerazione solo in classi dove Φ stata definita l’enumerazione stessa, e nelle loro sottoclassi. Se si necessita di riferirsi ad un tag di enumerazione definito da un’altra parte, bisogna "qualificarlo".

FRUIT_Apple // Senza qualifica.
EFruit.FRUIT_Apple // Se UnrealScript non lo riconosce, allora va qualificato cos∞.

Strutture

Una struttura Φ un modo di fondere un insieme di variabili in un nuovo tipo di super-variabile chiamata struct. Le strutture dell’UnrealScript sono come quelle del C, poichΘ possono contenere variabili semplici o array. Questa Φ la dichiarazione di una struttura:

// Un vettore nello spazio tridimensionale.
struct Vector
{
	var float X;
	var float Y;
	var float Z
};

Una volta dichiarata una struttura si pu≥ dichiarare variabili specifiche appartenenti al tipo di quella struttura.

// Dichiara due variabili di tipo Vector
var Vector Position;
var Vector Destination;

Per accedere ad un componente di una struttura, si usa il seguente codice:

function MyFunction()
{
	Local Vector A, B, C;
	 
	// Somma i primi due vettori nel terzo
	C = A + B;
 
	// Aggiunge solo la componente X	
        C.X = A.X + B.X;
 
	// Passa come argomento il vettore C ad una funzione
	SomeFunction( C );
 
	// Passa certi componenti di due vettori ad una funzione
	OtherFunction( A.X, C.Z );
} 

Ogni operazione con le normali variabili Φ possibile anche per le variabili struttura: si pu≥ assegnare variabili, passarle a funzioni, e accedere ai suoi componenti. Ci sono certe strutture definite nella classe Object che sono usate ovunque in Unreal. Si dovrebbe acquisire familiaritα con le loro operazioni in quanto sono blocchi fondamentali di script.

Vector: un punto o vettore nello spazio, con un componente X, Y e Z.

Plane: definisce un piano nello spazio. Un piano Φ definito dai suoi componenti X, Y e Z nonchΘ il componente W, che rappresenta la distanza del piano dall’origine lungo la normale del piano ( ossia la retta perpendicolare al piano passante per l’origine )

Rotation: una rotazione che definisce un sistema cartesiano di coordinate. Una rotazione contiene i componenti Pitch, Yaw e Roll.

Coords: un sistema di coordinate arbitrarie nello spazio.

Color: un valore RGB di colore.

Region: definisce una regione convessa nel livello.

Espressioni

Costanti

In UnrealScript si possono specificare valori costanti per tutti i tipi di dati:

Interi e byte costanti vengono specificati con numeri semplici, es. 123.

Se si vuole specificare interi e byte costanti in notazione esadecimale, 0x123

Numeri in virgola mobile costanti vanno specificati con i decimali, es.456.789

Stringhe costanti vanno specificate tra virgolette, es. "MyString"

Nomi costanti devono essere specificate fra apici, es. ‘MyName’

Vettori costanti contengono valori per X, Y e Z: es. Vect(1.0, 2.0, 4.0 )

Rotazioni costanti contengono Pitch, Yaw e Roll definiti: Rot(0x8000, 0x4000, 0)

La costante "None" si riferisce a "no object" o "no actor"

La costante "Self" si riferisce a "this object" o "this actor", ossia l’oggetto/actor del script in esecuzione

Costanti generali di oggetti sono specificate dal tipo di oggetti seguito dal nome dell’oggetto fra apici, es. texture’Default’

EnumCount fornisce il numero di elementi in una enumerazione, es. EnumCount(ELightType)

ArrayCount fornisce il numero di elementi in un array, es. ArrayCount(Touching)

Si pu≥ utilizzare la parola chiave "const" per dichiarare costanti alle quali ci si vuole in seguito riferire per nome. Esempio:

const LargeNumber=123456;
const PI=3.14159;
const MyName="Tim";
const Northeast=Vect(1.0,1.0,0.0);

Le costanti possono essere definite all’interno di classi o di strutture. Per accedere ad una costante dichiarata in un’altra classe, si usa la sintassi "classname.constname". Esempio:

Pawn.LargeNumber

Espressioni

Per assegnare un valore ad una variabile si usa l’operatore " = " :

function Test()
{
	local int i;
	local string[80] s;
	local vector v, q;
 
	i = 10; // Assegna un valore alla variabile intera i.
	s = "Hello!"; // Assegna un valore alla variabile stringa s
	v = q; // Copia il valore di q in v.
}

In UnrealScript ogni volta che una funzione o altra espressione richiede un certo tipo di dati ( es. un "float" ) e viene invece specificato un tipo differente ( es. un "int" ), il compilatore prova a convertire il valore nel tipo appropriato. Le conversioni fra tipo di dati numerici ( byte, int e float ) avvengono automaticamente senza intervento del programmatore. UnrealScript Φ inoltre in grado di effettuare altre conversioni, se esplicitate nel codice:

function Test()
{
	local int i;
	local string[80] s;
	local vector v, q;
	local rotation r;
 
	s = string(i); // Converte i ad una stringa e lÆassegna a s.
	s = string(v); // Converte v ad una stringa e lÆassegna a s.
	v = q + vector(r); // Converte rotation r ad un vector, aggiunge q e assegna a v.
}

Ecco un completo set di conversioni non automatiche da poter utilizzare:

String a Byte, Int, Float: converte una stringa come "123" ad un valore come 123. Se la stringa non rappresenta un valore, il risultato Φ 0.

Byte, Int, Float, Vector, Rotation a String: converte il numero nella sua rappresentazione testuale.

String a Vector, Rotation: prova a convertire una stringa nella sua rappresentazione vettoriale.

String a Bool: converte le parole ( case-insensitive ) "True" e "False" ai valori True e False; converte ogni valore diverso da zero in True, lo zero in False.

Bool a String: il risultato pu≥ essere "True" o "False".

Byte, Int, Float, Vector, Rotation a Bool: converte valori diversi da zero in True, lo zero in False.

Bool a Byte, Int, Float: converte True a 1, False a 0.

Name a String: converte il nome nell’equivalente testuale

Rotation a Vector: ritorna un vettore convertendo le rotazioni nei tre assi.

Vector a Rotation: ritorna una rotazione di Pitch e Yaw definiti dal vettore, mentre Roll Φ zero.

Oggetto ( o Actor) a Int: ritorna un intero garantito unico per quell’oggetto.

Oggetto ( o Actor) a Bool: ritorna False se l’oggetto Φ None, True nel caso contrario.

Oggetto ( o Actor) a String: ritorna una rappresentazione testuale dell’oggetto.

Convertire riferimenti ad oggetti fra le classi

Come le funzioni di conversione precedenti, che convertono in tipi di dati comuni, in UnrealScript Φ possibile convertire riferimenti ad actors e oggetti tra vari tipi. Per esempio, tutti gli actors hanno una variabile chiamata "Target", che Φ un riferimento ad un altro actor. Poniamo di scrivere uno script in cui si vuole controllare se il proprio Target appartiene alla classe "Pawn" perchΘ per fare una certa azione il proprio target non deve appartenere ai pawns.

var actor Target;
//...
 
function TestActorConversions()
{
	local Pawn P;
 
	// Cerca se lÆactor Φ un pawn
	P = Pawn(Target);
	if( P != None )
	{
		// Il target Φ un pawn, allora imposta lÆEnemy su Self
		P.Enemy = Self;
	}
	else
	{
		// Il target non Φ un pawn
	}
}

Per effettuare una conversione di actor, scrivere il nome della classe seguito dall’espressione dell’actor da convertire, tra parentesi. Una tale conversione pu≥ avvenire o fallire. Nell’esempio sopra, se il Target si riferisce ad un oggetto Trigger invece di un pawn, l’espressione Pawn(Target) ritorna "None" poichΘ un Trigger non pu≥ essere convertire ad un Pawn. Comunque, se il proprio Target si riferisce ad un oggetto Brute, la conversione con successo ritorna il Brute poichΘ Brute Φ una sottoclasse di Pawn. Cos∞, le conversioni di actors hanno due scopi: primo, si possono usare per controllare se un riferimento ad un actor appartiene ad una certa classe; secondo, si possono usare per convertire un riferimento ad un actor da una classe ad una pi∙ specifica. Notare che queste conversioni non toccano per nulla l’actor da convertire, esse semplicemente abilitano l’UnrealScript a trattare il riferimento all’actor come se fosse di un tipo pi∙ specifico. Un altro esempio di conversioni si trova nello script Inventory. Ogni actor Inventory Φ posseduto da un Pawn, anche se la sua variabile Owner pu≥ riferirsi a qualsiasi Actor. Perci≥ il tema ricorrente del codice Inventory Φ di lanciare Owner ad un Pawn.

// Chiamata dallÆengine quando viene distrutto.
function Destroyed()
{
	// Rimuove dallÆinventario dellÆOwner.
	if( Pawn(Owner)!=None )
		Pawn(Owner).DeleteInventory( Self );
} 

funzioni

Dichiarare funzioni

In UnrealScript si possono dichiarare nuove funzioni e scrivere nuove versioni di funzioni esistenti. Le funzioni accettano uno o pi∙ parametri ( di ogni tipo di variabile supportato da UnrealScript ) a possono opzionalmente ritornare un valore. Anche se la gran parte delle funzioni sono scritte direttamente in UnrealScript, si possono anche dichiarare funzioni richiamabili da UnrealScript e scritte in C++ su una DLL. La tecnologia di UT supporta tutte le possibili combinazioni di chiamate di funzioni: l’engine C++ pu≥ chiamare funzioni script; uno script pu≥ chiamare funzioni C++; uno script pu≥ chiamare script. Ecco una semplice dichiarazione di funzione. Questa prende un vector come parametro e ritorna un float.

// Funzione per trovare il modulo di un vettore.
function float VectorSize( vector V )
{
	return sqrt( V.X * V.X + V.Y * V.Y + V.Z * V.Z );
}

La parola "function" precede sempre una dichiarazione di funzione. E’ seguita dal valore opzionale di ritorno ( in questo caso un float ), poi il nome della funzione e infine la lista dei parametri tra parentesi. Quando viene chiamata una funzione, il codice fra graffe viene eseguito. All’interno della funzione si possono dichiarare variabili locali ( usando la parola chiave "local" ) ed eseguire qualsiasi codice UnrealScript. La parola chiave opzionale "return" ritorna il valore seguente. Si pu≥ passare ad una funzione ogni tipo di dati di UnrealScript ( inclusi gli array ) ed una funzione pu≥ ritornare qualunque valore. Per default ogni variabile locale dichiarata in una funzione Φ inizializzata a zero. Le chiamate di funzione possono essere ricorsive. Per esempio, la seguente funzione calcola il fattoriale di un numero:

function int Factorial( int Number )
{
	if( Number <= 0 )
		return 1;
	else
		return Number * Factorial( Number û 1 );
}

Alcune funzioni di UnrealScript sono chiamate dall’engine ogni volta che avvengono certi eventi. Per esempio, quando un actor viene toccato da un altro actor, l’engine chiama la sua funzione "Touch" per dirgli chi lo sta toccando. Scrivendo una funzione "Touch" personalizzata si possono associare azioni speciali all’evento.

// Chiamata ogni volta che tocca questo actor
function Touch( actor Other )
{
	Log( "I was touched!")
	Other.Message( "You touched me!" );
}

La funzione sopra illustra alcuni aspetti. In primo luogo, la funzione scrive un messaggio nel file log usando il comando "Log" ( che Φ l’equivalente del "printf" del C o del "cout" del C++ ). Secondo, essa chiama la funzione "Message" presente nell’actor Other. Chiamare funzioni in altri actors Φ una azione comune in UnrealScript ed in linguaggi orientati agli oggetti quali Java, poichΘ permette un metodo semplice di comunicazione fra gli actors.

Specificatori di parametri di una funzione

Normalmente quando si chiama una funzione UnrealScript crea una copia locale dei parametri passati alla funzione. Se la funzione modifica alcuni di questi parametri, questi non hanno effetto sulle variabili passate. Per esempio:

function int DoSomething( int x )
{
	x = x * 2;
	return x;
}
function int DoSomethingElse()
{
	local int a, b;
 
	a = 2;
	log( "The value of a is " $ a );
 
	b = DoSomething( a );
	log( "The value of a is " $ a );
	log( "The value of b is " $ b );
}

Questo produce il seguente output quando DoSomethingElse viene chiamato:

The value of a is 2
The value of a is 2
The value of b is 4

In altre parole, la funzione DoSomething ha accettato a come parametro creando una sua copia locale in maniera tale da non modificare il valore della variabile reale "a". Lo specificatore "out" indica ad una funzione che deve modificare la variabile passata e non crearne una locale ( corrisponde al passaggio di parametri per riferimento mediante puntatori in C/C++ ). Questo Φ utile per esempio se si ha una funzione che necessita di ritornare pi∙ valori. Basta impostare le variabili come parametri "out":

// Calcola il minimo ed il massimo componente di un vettore
function VectorRange( vector V, out float Min, out float Max )
{
	// Calcola il minimo
	if ( V.X<V.Y && V.X<V.Z ) Min = V.X;
	else if( V.Y<V.Z ) Min = V.Y;
	else Min = V.Z;
 
	// Calcola il massimo
	if ( V.X>V.Y && V.X>V.Z ) Max = V.X;
	else if( V.Y>V.Z ) Max = V.Y;
	else Max = V.Z;
}

Senza la parola chiave "out" sarebbe lungo scrivere funzioni che ritornano pi∙ valori. Con la parola chiave "optional" si possono rendere certi parametri di una funzione opzionali, per convenienza di chi chiama la funzione. Per le funzioni dell’UnrealScript, i parametri opzionali che non vengono specificati sono posti a zero. Per le funzioni intrinsic, il valore di default dei parametri opzionali dipende dalla funzione. Per esempio, la funzione Spawn accetta una location e rotation opzionali. La parola chiave "coerce" forza i parametri ad essere convertiti ad un tipo specifico ( anche se UnrealScript normalmente non effettuerebbe la conversione ). Questo Φ utile per funzioni che trattano stringhe cosicchΦ i parametri sono automaticamente convertiti in stringhe.

Ridefinizione di funzioni

La ridefinizione di funzioni si riferisce alla riscrittura di una nuova versione di una funzione in una sottoclasse. Per esempio, si scrive lo script per un nuovo tipo di mostro chiamato Demon. La classe Demon appena creata deriva dalla classe Pawn. Adesso, quando un pawn vede il giocatore per la prima volta, viene chiamata la funzione "SeePlayer" del pawn di modo che il pawn attacchi il giocatore. Questo Φ un concetto valido, tuttavia possiamo volere che la funzione "SeePlayer" agisca in un altro modo nella classe Demon. Il metodo da adottare quindi Φ ridefinire la funzione. Per farlo, basta tagliare ed incollare la definizione di funzione dalla classe genitrice nella nuova classe. Per esempio:

// Nuova versione della funzione Touch per la classe Demon
function SeePlayer( actor SeenPlayer )
{
	log( "The demon saw a player" );
	// Aggiungere qui nuove funzionalitαà
}

La ridefinizione di una funzione Φ la chiave per creare efficienti nuove classi in UnrealScript. Si pu≥ creare una nuova classe che derivi da una esistente; poi bisogna solo ridefinire le funzioni che si vuole abbiamo un altro comportamento. Ci≥ permette di creare nuovi tipi di oggetti senza scrivere enormi parti di codice. Alcune funzioni in UnrealScript sono dichiarate come "final". La parola chiave "final" ( che compare immediatamente prima della parola "function" ) significa "questa funzione non pu≥ essere ridefinita da classi derivate". Questo andrebbe usato in funzioni di cui non si dovrebbe effettuare la ridefinizione, per motivi di prestazioni. Per esempio si ha la funzione "VectorSize" che calcola il modulo di un vettore. Non c’Φ alcuna ragione per ridefinire una tale funzione, cos∞ viene dichiarata come final. D’altra parte, una funzione come "Touch" Φ molto dipendente dal contesto e non dovrebbe essere final.

Specificatori avanzati di funzioni

Static: una funzione static si comporta come una funzione C globale poichΘ pu≥ essere chiamata senza il riferimento ad un oggetto di una classe. Le funzioni static possono chiamare altre funzioni statiche ed accedere ai valori delle variabili. Le funzioni static non possono invece chiamare funzioni non statiche e non possono accedere a variabili di istanza ( poichΘ non sono eseguite nel rispetto di una istanza di un oggetto ). Diversamente da linguaggi come il C++, le funzioni statiche sono virtuali e possono essere ridefinite nelle classi derivate. Ci≥ Φ utile nei casi in cui si voglia chiamare una funzione static in una classe variabile ( una classe non nota al momento della compilazione, ma riferita tramite una variabile o una espressione ).

Singular: la parola chiave "singular", che appare immediatamente prima della dichiarazione di una funzione, evita che una funzione si richiami ricorsivamente. La regola Φ questa: se un certo actor Φ giα nel mezzo di una singola funzione, ogni seguente chiamata a singole funzioni viene saltata. E’ utile per evitare di incorrere in ricorsivi infiniti bug in certi casi. Per esempio, se si prova a muovere un actor all’interno della funzione "Bump", c’Φ una buona probabilitα che l’attore si scontrerα con un altro actor durante il suo movimento, col risultato che vi sarα un’altra chiamata alla funzione Bump, e cos∞ via. Bisogna essere molto attenti ad evitare tali comportamenti, ma se non si pu≥ essere sicuri di evitare questi problemi Φ meglio utilizzare "singular".

Intrinsic: si possono dichiarare le funzioni come "intrinsic", che significa che la funzione Φ richiamabile da UnrealScript, ma Φ in realtα scritta altrove in C++. Per esempio, la classe Actor contiene molte definizioni di funzioni intrinsic, come:

intrinsic(266) final function bool Move( vector Delta );

Il numero tra parentesi dopo la parola chiave "intrinsic" corrisponde al numero della funzione come dichiarata in C++ ( utilizzando la macro AUTOREGISTER_INTRINSIC ). La funzione intrinsic si ritiene si trovi nella DLL col nome identico al pacchetto contenente la classe.

Latent: dichiara che una funzione intrinsic Φ latente, cioΦ che pu≥ essere chiamata solo da codice di stato e pu≥ ritornare dopo il passaggio di una certa quantitα di tempo.

Iterator: dichiara che una funzione intrinsic Φ un iteratore, che pu≥ essere usato per effettuare un ciclo fra una lista di actors usando il comando "foreach".

Simulated: dichiara che una funzione pu≥ eseguire dalla parte del client quando un actor Φ un simulated proxy oppure un autonomous proxy. Tutte le funzioni che sono sia intrinsic che final risultano automaticamente simulated.

Operator, PreOperator, PostOperator: queste parole chiave servono a dichiarare un tipo speciale di funzione chiamato operatore ( equivalente agli operatori C++): alcuni sono "+", "-", "==", e "||". Il concetto di operatori Φ simile a quello del C++ e si possono dichiarare nuove funzioni di operatori e parole chiave come funzioni UnrealScript o intrinsic.

struttura del programma

UnrealScript supporta tutte le dichiarazioni di controllo di flusso del C/C++/Java.

Cicli For

I cicli for consentono di ripetere un blocco finchΦ una condizione viene raggiunta. Per esempio:

function ForExample()
{
	local int i;
	log( "Dimostrazione del ciclo for" );
	for( i=0; i<4; i++ )
	{
		log( "Il valore di i Φ " $ i );
	}
	log( "Completato con i=" $ i);
}

L’output di questo ciclo Φ:

Dimostrazione del ciclo for
Il valore di i Φ 0
Il valore di i Φ 1
Il valore di i Φ 2
Il valore di i Φ 3
Completato con i=4

In un ciclo for bisogna specificare tre espressioni separate divise da punto e virgola. La prima Φ l’inizializzazione di una variabile al suo valore iniziale. La seconda Φ la condizione finale, per la quale finchΦ risulta vera il ciclo continua. La terza fornisce una espressione che incrementa la variabile contatore ad ogni ripetizione del ciclo. Anche se la maggior parte dei cicli for aggiornano giusto il contatore, si possono usare i cicli for anche per situazioni avanzate quali riempire un array, ecc. E’ possibile inserire un singolo comando, senza parentesi:

for( i=0; i<4; i++ )
	log( "Il valore di i Φ " $ i );

Oppure comandi multipli fra parentesi graffe:

for( i=0; i<4; i++ )
{
	log( "Il valore di i Φ" );
	log( i );
}

Cicli Do-Until

I cicli Do-Until consentono di ripetere un ciclo finchΦ una espressione finale risulta vera; in questo tipo di ciclo le operazioni vengono eseguite almeno una volta, a differenza del while. Notare che la sintassi Φ diversa da quella C/Java ( che utilizza do-while ).

// Esempio di un ciclo do
function DoExample()
{
	local int i;
	log( "Dimostrazione del ciclo do " );
	do
	{
		log( "Il valore di i Φ" $ i );
		i = i + 1;
	} until( i == 4 );
	log( "Completato con i=" $ i);
}

L’output di questo ciclo Φ:

Dimostrazione del ciclo do
Il valore di i Φ 0
Il valore di i Φ 1
Il valore di i Φ 2
Il valore di i Φ 3
Completato con i=4

Cicli While

I cicli while permettono di eseguire un ciclo solo se una espressione iniziale Φ vera.

function WhileExample()
{
	local int i;
	log( "Dimostrazione del ciclo while" );
	while( i < 4 )
	{
		log( "Il valore di i Φ " $ i );
		i = i + 1;
	}
	log( "Completato con i=" $ i);
}

L’output di questo ciclo Φ:

Dimostrazione del ciclo while
Il valore di i Φ 0
Il valore di i Φ 1
Il valore di i Φ 2
Il valore di i Φ 3
Completato con i=4

Break

Il comando break termina il ciclo corrente ( for, do o while ).

function BreakExample()
{
	local int i;
	log( "Dimostrazione del break" );
	for( i=0; i<10; i++ )
	{
		if( i == 3 )
			break;
		log( "Il valore di i Φ " $ i );
	}
	log( "Completato con i=" $ i );
}

L’output di questo ciclo Φ:

Dimostrazione del break
Il valore The value of i is 0
Il valore di i Φ 0
Il valore di i Φ 1
Il valore di i Φ 2
Completato con i=3

Goto

Il comando goto va ad una etichetta da qualche parte della funzione o stato corrente.

function GotoExample()
{
	log( "Inizio di GotoExample" );
	goto Lα;
Qui:
	log( "Siamo qui" );
	goto Laggi∙;
Lα:
	log( "Siamo lα" );
	goto Qui;
Laggi∙:
	log( "Siamo laggi∙" );
}

L’output Φ:

Inizio di GotoExample
Siamo lα
Siamo qui
Siamo laggi∙

Condizionali

"If, "Else If" e "Else" permettono di eseguire codice solo se sono raggiunte certe condizioni.

// Esempio di semplice if.
if( LightBrightness < 20 )
	log( "La mia luce Φ fioca" );
 
// Esempio di if-else
if( LightBrightness < 20 )
	log( "La mia luce Φ fioca" );
else
	log( "La mia luce Φ alta" );
 
// Esempio di if-else ed if-else
if( LightBrightness < 20 )
	log( "La mia luce Φ fioca" );
else if( LightBrightness < 40 )
	log( "La mia luce Φ media" );
else if( LightBrightness < 60 )
	log( "La mia luce Φ piuttosto alta" );
else
	log( "La mia luce Φ alta" );
 
// Esempio di if fra parentesi
if( LightType == LT_Steady )
{
	log( "La luce Φ fissa" );
}
else
{
	log( "La luce non Φ fissa" );
}

Switch-Case

"Switch", "Case", "Default" e "Break" permettono di gestire pi∙ ordinatamente liste di condizioni.

function TestSwitch()
{
	// Esegue uno dei case sotto elencati in base al valore di LightType
	switch( LightType )
	{
		case LT_None:
			log( "Non cÆΦ luce" );
			break;
		case LT_Steady:
			log( "CÆΦ luce fissa" );
			break;
		case LT_Backdrop:
			log( "CÆΦ luce di fondo" );
			break;
		default:
			log( "La luce Φ dinamica" );
			break;
	}
}

Uno "switch" consiste di uno o pi∙ "case" e opzionalmente di un "default". Dopo l’esecuzione dello switch su di un parametro ( LightType ), vengono controllati tutti i case ed eseguito quello adatto al valore del parametro; se non si trova riscontro in nessuno dei case, viene eseguito il default ( se presente ). Il comando break consente di saltare gli altri case dopo averne eseguito uno.

stati

In generale

Storicamente, i programmatori di giochi hanno sempre usato il concetto di stati. Gli stati sono un metodo naturale di gestire comportamenti complessi di oggetti. Comunque, prima di UnrealScript, gli stati non erano mai stati supportati dal linguaggio e dovevano essere implementati mediante switch, il che era pi∙ complesso da gestire ed aggiornare. UnrealScript invece supporta gli stati a livello di linguaggio. Ogni actor nel livello Φ sempre in un solo particolare stato. Il suo stato riflette le azioni che deve compiere. Per esempio, i mover hanno molti stati quali "StandOpenTime" e "BumpOpenTime". I pawn hanno vari stati come "Dying", "Attacking" e "Wandering". In UnrealScript si possono scrivere funzioni e codice per un particolare stato. Queste funzioni sono chiamate solo quando l’actor si trova in quello stato. Per esempio, si sta scrivendo lo script di un mostro e si pensa a come voler gestire la funzione "SeePlayer". Quando il mostro gira per il livello, deve attaccare il giocatore che vede senza interrompere l’attacco. Il modo pi∙ semplice per fare ci≥ Φ definire vari stati ( Wandering e Attacking ) e scrivere una diversa versione di Touch per ogni stato.

Prima di procedere, bisogna capire che ci sono due grandi benefici negli stati ma anche una complicazione:

Beneficio: gli stati permettono di avere un semplice strumento per scrivere funzioni specifiche per lo stato corrente, in modo tale da gestire la stessa funzione differentemente per ogni stato.

Beneficio: con uno stato si pu≥ scrivere speciale codice di stato, usando tutti i comandi normali dell’UnrealScript pi∙ le funzioni latenti. Un funzione latente Φ una funzione che esegue lentamente e ritorna dopo che Φ passato un certo tempo. Questo permette di costruire una programma basato sul tempo, cosa che C/C++/Java non permette. In pi∙ la scrittura del codice Φ estremamente intuitiva: si pu≥ scrivere il corrispondente di "apri questa porta; aspetta 2 secondi; esegui quell’effetto sonoro; apri quella porta; libera quel mostro e fallo attaccare il giocatore" con semplice codice lineare, mentre l’engine di UT si occupa della esecuzione dipendente dal tempo.

Complicazione: avendo ridefinito una funzione ( quale Touch ) in molti stati e nelle classi derivate, risulta complicato riconoscere quale esatta versione della funzione viene richiamata in uno specifico contesto. UnrealScript fornisce regole che delineano chiaramente questo processo, ma Φ comunque qualcosa a cui bisogna prestare molta attenzione quando si creano complesse gerarchie di classi e stati.

Questo Φ un esempio di stati per lo script della TriggerLight:

// Il trigger accende la luce
state() TriggerTurnsOn
{
	function Trigger( actor Other, pawn EventInstigator )
	{
		Trigger = None;
		Direction = 1.0;
		Enable( 'Tick' );
	}
}
 
// Il trigger spegne la luce
state() TriggerTurnsOff
{
	function Trigger( actor Other, pawn EventInstigator )
	{
		Trigger = None;
		Direction = -1.0;
		Enable( 'Tick' );
	}
}

Qui si dichiarano due stati differenti ( TriggerTurnsOn e TriggerTurnsOff) e si sta scrivendo una versione differente della funzione trigger per ogni stato. Comunque gli stati garantiscono la pi∙ semplice espandibilitα del codice originario. Uno stato pu≥ essere dichiarato modificabile, cioΦ l’utente pu≥ impostarlo attraverso UnrealEd. Per dichiarare modificabile uno stato, si procede cos∞:

state() MyState
{
	//...
}

Per dichiararlo non modificabile:

state MyState
{
	//...
}

Si pu≥ anche specificare lo stato iniziale, o automatico, che un actor dovrebbe avere mediante la parola chiave "auto". Questo permette agli actor di essere posizionati in quello stato quando vengono per la prima volta attivati.

auto state MyState
{
	//...
}

Etichette di uno stato e funzioni latenti

Oltre alle funzioni, uno stato pu≥ contenere una o pi∙ etichette seguite da codice UnrealScript. Per esempio:

auto state MyState
{
Begin:
	Log( "MyState Φ iniziato" );
	Sleep( 2.0 );
	Log( "MyState Φ terminato in sleep" );
	goto Begin;
}

Il codice sopra stampa il messaggio "MyState Φ iniziato", poi resta in pausa per due secondi e infine stampa il messaggio "MyState" Φ terminato in sleep". La cosa interessante in questo esempio Φ la chiamata alla funzione latente "Sleep": questa chiamata a funzione non ritorna immediatamente, bens∞ dopo che Φ passato un certo tempo di gioco. Le funzioni latenti possono essere chiamate solo all’interno di uno stato e non all’interno di funzioni. Le funzioni latenti permettono di gestire catene complesse di eventi che comportano il passaggio del tempo. Tutto il codice di stato inizia con una etichetta; nell’esempio sopra l’etichetta si chiama "Begin". L’etichetta fornisce un punto di entrata conveniente nel codice di stato. Si pu≥ usare qualsiasi nome di etichetta nel codice di stato, ma "Begin" Φ speciale: Φ il punto d’inizio di default in quello stato. Ci sono tre principali funzioni latenti disponibili per tutti gli actors:

Sleep( float Secondi ) mette in pausa l’esecuzione dello stato per un certo tempo, poi continua.

FinishAnim( ) aspetta finchΦ l’animazione corrente termina, poi continua. Questa funzione rende facile scrivere script orientati alle animazioni, ossia script la cui esecuzione Φ governata dalle animazioni dei mesh. Per esempio, molti degli script AI sono orientati alle animazioni ( al contrario dell’orientamento al tempo ) perchΘ le animazioni fluide sono fondamentali nel sistema di AI.

FinishInterpolation( ) aspetta il completamento del corrente movimento di InterpolationPoint, poi continua.

La classe Pawn definisce alcune importanti funzioni latenti per azioni quali la navigazione attraverso il livello ed i movimenti a breve termine. Ci sono tre funzione intrinsic UnrealScript che sono particolarmente utili nella scrittura di codice di stato:

La funzione "Goto" ( simile al C/C++/Basic goto ) all’interno di uno stato causa quest’ultimo a continuare ad eseguire ad una etichetta differente.

Il comando speciale Goto(‘’) all’interno di uno stato causa l’arresto dell’esecuzione del codice. L’esecuzione non continua finchΦ non si cambia etichetta o si passa ad un nuovo stato.

La funzione "GotoState" fa passare l’actor ad un nuovo stato e opzionalmente continua ad una etichetta specificata ( se non viene specificata, il default Φ l’etichetta "Begin" ). Si pu≥ chiamare GotoState all’interno di uno stato, e si passa immediatamente alla destinazione specificata. Si pu≥ inoltre chiamare GotoState all’interno di qualunque funzione nell’actor, ma ci≥ non ha effetto immediato: agisce solo quando l’esecuzione torna a codice di stato.

Ecco un esempio dei concetti sopra descritti:

// Questo Φ lo stato automatico da eseguire.
auto state Idle
{
	// Quando viene toccato da un altro actorà
	function Touch( actor Other )
	{
		log( "Sono stato toccato, quindi attacco" );
		GotoState( æAttackingÆ );
		Log( "Sono entrato nello stato di attacco" );
	}
Begin:
	log( "Sono in attesaà" );
	sleep( 10 );
	goto æBeginÆ;
}
 
// Stato di attacco.
state Attacking
{
Begin:
	Log( "Sto eseguendo il codice di attacco" );
	//...
}

Quando viene eseguito questo programma e si viene toccati, si vede:

Sono in attesaà
Sono in attesaà
Sono in attesaà
Sono stato toccato, quindi attacco
Sono entrato nello stato di attacco
Sto eseguendo il codice di attacco

Bisogna essere sicuri di aver compreso questo importante aspetto di GotoState: quando si chiama GotoState da una funzione, non si passa alla destinazione immediatamente ma solo dopo essere entrati nel codice di stati.

Regole di ereditarietA' di stato e regole di contesto

In UnrealScript, quando si crea una sottoclasse, la nuova classe eredita tutte le variabili, funzioni e stati dalla classe genitrice. Questo Φ chiaro. Per≥ l’aggiunta degli stati in UnrealScript estende le regole di ereditarietα e le regole di contesto. Le regole di ereditarietα sono:

Una nuova classe eredita tutte le variabili della classe genitrice.

Una nuova classe eredita tutte le funzioni non di stato dalla classe genitrice. Si possono ridefinire funzioni o scriverne di nuove.

Una nuova classe eredita tutti gli stati della classe genitrice, incluse le funzioni ed etichette degli stati. Si possono ridefinire le funzioni degli stati e le etichette, aggiungere nuove funzioni e nuove etichette.

Ecco un esempio di tutte le regole di ridefinizione:

// Esempio di classe genitrice.
class MyParentClass expands Actor;
 
// Funzione non di stato
function MyInstanceFunction()
{
	log( "Eseguo MyInstanceFunction" );
}
 
// Uno stato
state MyState
{
	// Funzione di stato
	function MyStateFunction()
	{
		Log( "Eseguo MyStateFunction" );
	}
// Etichetta Begin
Begin:
	Log("Inizio MyState");
}
 
// Esempio di classe derivata
class MyChildClass expands MyParentClass;
 
// Ridefinizione di funzione non di stato
function MyInstanceFunction()
{
	Log( "Eseguo MyInstanceFunction nella classe derivata" );
}
 
// Qui dichiaro MyState in modo da poter ridefinire MyStateFunction.
state MyState
{
	// Ridefinisco MyStateFunction
	function MyStateFunction()
	{
		Log( "Eseguo MyStateFunction" );
	}
// Ridefinisco lÆetichetta Begin
Begin:
	Log( "Inizio MyState in MyChildClass" );
}

Quando si ha una funzione implementata globalmente, in uno o pi∙ stati, ed in una o pi∙ classi genitrici, bisogna capire quale versione della funzione verrα chiamata in un dato contesto. Le regole d contesto, che risolvono queste situazioni complesse, sono:

Se l’oggetto Φ in uno stato, ed una implementazione della funzione esiste da qualche altra parte in quello stato ( sia nella classe dell’actor che in una qualche classe genitrice ), viene chiamata la versione della funzione pi∙ derivata.

In caso contrario, viene chiamata la versione della funzione non di stato pi∙ derivata.

Programmazione di stato avanzata

Se uno stato non ridefinisce uno stato dello stesso nome nella classe genitrice, allora si pu≥ opzionalmente utilizzare la parola chiave "expands" per derivare uno stato esistente nella classe corrente. Questo Φ utile per esempio nelle situazioni in cui si abbia stati simili ( come MeleeAttacking e RangeAttacking ) che hanno molte funzionalitα in comune. In questo caso si pu≥ dichiarare uno stato di base Attacking:

state Attacking
{
	// Qui si trovano le funzioni di baseà
} 
 
// Attacco da vicino 
state MeleeAttacking expands Attacking
{
	// Qui si trovano le funzioni specificheà
} 
 
// Attacco da distanza
state RangeAttacking expands Attacking
{
	// Qui si trovano le funzioni specificheà
} 

Uno stato pu≥ opzionalmente usare lo specificatore "ignores" per ignorare funzioni quando si trova in uno stato. La sintassi Φ:

state Retreating
{
	// Ignora i seguenti messaggi.
	ignores Touch, UnTouch, MyFunction;
 
	// Qui si trovano le funzioni specificheà
}

Si pu≥ dire in quale specifico stato si trovi un actor dalla sua variabile "state", una variabile di tipo "name". E’ possibile per un actor non essere in alcun stato usando GotoState(‘’). Quando un actor non Φ in alcun stato, vengono chiamate solo le sue funzioni globali non di stato. Ogni volta che si usa il comando GotoState per impostare lo stato di un actor, l’engine chiama due speciali funzioni di notifica, se sono state definite: EndState( ) e BeginState( ). EndState viene chiamata nello stato corrente immediatamente prima l’inizio di un nuovo stato, e BeginState viene chiamata subito dopo l’inizio del nuovo stato. Queste funzioni offrono un posto conveniente dove effettuare ogni inizializzazione di stato o rimozione che lo stato richieda.

funzionalitA' del linguaggio

Operatori e loro precedenze

UnrealScript fornisce a vasta gamma di operatori in stile C/C++/Java per effettuare operazioni come somma, comparazione ed incremento. Il set completo Φ definito in Object.c, ma qui c’Φ una ricapitolazione. Questi solo gli operatori standard, in ordine di precedenza. Notare che la precedenza Φ quella del C.

Operatore Applicabile a tipi Significato
$ String Concatenazione di stringhe
*= byte, int, float, vector, rotation Moltiplica e assegna
/= byte, int, float, vector, rotation Divide e assegna
+= byte, int, float, vector Somma e assegna
-= byte, int, float, vector Sottrae e assegna
|| Bool OR logico
&& Bool AND logico
& Int AND bit a bit
| Int OR bit a bit
^ Int OR esclusivo bit a bit
!= Tutti Comparazione per disuguaglianza
== Tutti Comparazione per uguaglianza
< byte, int, float, string Minore
> byte, int, float, string Maggiore
<= byte, int, float, string Minore o uguale
>= byte, int, float, string Maggiore o uguale
~= float, string Quasi uguale (nell’ordine dello 0.0001)
<< int, vector Spostamento verso sinistra di un bit (int), Trasformazione in avanti (vector)
>> int, vector Spostamento verso destra di un bit (int), Trasformazione all’indietro (vector)
+ byte, int, float, vector Somma
- byte, int, float, vector Sottrazione
% Float Modulo ( resto della divisione )
* byte, int, float, vector, rotation Prodotto
/ byte, int, float, vector, rotation Quoziente
Dot Vector Prodotto scalare
Cross Vector Prodotto vettoriale
** Float Esponenziale

Quando si scrive una espressione complessa quale "1*2+3*4", UnrealScript automaticamente isola gli operatore per precedenza. PoichΘ il prodotto ha pi∙ alta precedenza della somma, l’espressione equivale a "(1*2)+(3*4)". Gli operatori &&" e "||" sono cortocircuitati: se il risultato pu≥ essere determinato solamente dalla prima espressione ( per esempio, se il primo argomento && Φ falso), la seconda espressione non viene considerata. Inoltre UnrealScript supporta i seguenti operatori unari:

! (bool) Not logico

- (int, float) negazione

~ (int) negazione bit a bit

++, -- incremento, decremento ( prima o dopo una variabile )

Funzioni di uso generico

Funzioni intere:

int Rand( int Max ); ritorna un valore random fra 0 e Max-1.

int Min( int A, int B ); ritorna il minore dei due numeri.

int Max( int A, int B ); ritorna il maggiore dei due numeri.

int Clamp( int V, int A, int B ); ritorna il primo numero pescato nell’intervallo fra A e B.

Funzioni in virgola mobile:

float Abs( float A ); ritorna il valore assoluto del numero.

float Sin( float A ); ritorna il seno del numero espresso in radianti.

float Cos( float A ); ritorna il coseno del numero espresso in radianti.

float Tan( float A ); ritorna la tangente del numero espresso in radianti.

float Atan( float A ); ritorna l’arcotangente del numero espresso in radianti.

float Exp( float A ); ritorna la costante "e" elevata alla A.

float Loge( float A ); ritorna il logaritmo naturale di A.

float Sqrt( float A ); ritorna la radice quadrata di A.

float Square( float A ); ritorna il quadrato di A.

float FRand(); ritorna un numero random fra 0.0 e 1.0.

float FMin( float A, float B ); ritorna il minore fra due numeri.

float FMax( float A, float B ); ritorna il maggiore fra due numeri.

float FClamp( float V, float A, float B ); ritorna il primo numero pescato nell’intervallo fra A e B.

float Lerp( float Alpha, float A, float B ); ritorna l’interpolazione lineare tra A e B.

float Smerp( float Alpha, float A, float B ); ritorna una interpolazione non lineare Alpha-smooth tra A e B.

Le funzioni su stringhe in UT hanno un aspetto simile al Basic:

int Len( coerce string[255] S ); ritorna la lunghezza di una stringa.

int InStr( coerce string[255] S, coerce string[255] t); ritorna l’offset della 2a stringa nella 1a se esiste, altrimenti dα –1.

string[255] Mid ( coerce string[255] S, int i, optional int j ); ritorna la parte media della stringa S, iniziando dal punto i e includendo j caratteri ( o tutti se j non viene specificato ).

string[255] Left ( coerce string[255] S, int i ); ritorna il carattere a sinistra di i in S.

string[255] Right ( coerce string[255] S, int i ); ritorna il carattere a destra di i in S.

string[255] Caps ( coerce string[255] S ); ritorna S convertito a maiuscole.

Funzioni sui vettori:

float Size( vector A ); ritorna il modulo del vettore.

vector Normal( vector A ); converte il vettore in un versore ( modulo 1.0 ).

Invert ( out vector X, out vector Y, out vector Z ); inverte un sistema di coordinate specificato da vettori dei 3 assi.

vector VRand ( ); ritorna un vettore random uniforme nelle 3 dimensioni.

float Dist ( vector A, vector B ); ritorna la distanza fra due punti.

vector MirrorVectorByNormal( vector Vect, vector Normal ); specchia un vettore rispetto ad una normale ( vettore ).

caratteristiche avanzate del linguaggio

Foreach e funzioni iteratrici

Il comando "foreach" rende facile operare con una grande quantitα di actors, per esempio tutti gli actors di un livello, oppure tutti gli actors ad una certa distanza da un altro actor. "Foreach" opera in congiunzione con uno speciale tipo di funzione chiamata "iteratrice" il cui scopo Φ di iterare lungo una lista di actors. Un esempio:

// Mostra una lista di tutte le luci del livello
function Something()
{
	local actor A;
 
	// Sfoglia tutti gli actors del livello
	log( "Lights:" );
	foreach AllActors( class æActorÆ, A )
	{
		if( A.LightType != LT_None )
			log( A );
	}
}

Il primo parametro in tutti i comandi foreach Φ una classe costante, che specifica che tipi di actors cercare. Si pu≥ usare questo per limitare la ricerca, per esempio, ai soli Pawns.

1. AllActors ( class BaseClass, out actor Actor, optional name MatchTag ); itera attraverso tutti gli actors del livello. Se si specifica un MatchTag opzionale, la ricerca viene limitata agli actors con quel tag.

2. ChildActors( class BaseClass, out actor Actor ); itera attraverso tutti gli actors appartenenti a quell’actor.

3. BasedActors( class BaseClass, out actor Actor ); itera attraverso tutti gli actors che stanno sopra quell’actor. TouchingActors( class BaseClass, out actor Actor ); itera attraverso tutti gli actors che stanno toccando quell’actor.

4. TraceActors( class BaseClass, out actor Actor, out vector HitLoc, out vector HitNorm, vector End, optional vector Start, optional vector Extent ); itera attraverso tutti gli actors che toccano una linea tracciata dal punto Start fino al punto End, usando un cilindro di collisione di estensione Extent. Ad ogni iterazione, HitLoc viene impostato al punto di contatto, mentre HitNorm rappresenta la normale passante per HitLoc.

5. RadiusActors( class BaseClass, out actor Actor, float Radius, optional vector Loc ); itera attraverso tutti gli actors ad un certo raggio da un punto specificato ( se nessun punto viene introdotto, il default Φ la locazione dell’actor ).

6. VisibleActors( class BaseClass, out actor Actor, optional float Radius, optional vector Loc ); itera attraverso una lista di tutti gli actors visibili da una locazione specificata ( se nessun punto viene introdotto, il default Φ la locazione dell’actor ).

Specificatori di chiamata a funzione

In situazioni complesse di programmazione, si renderα necessario chiamare una versione specifica di una funzione piuttosto che quella nel proprio ambito. Per gestire queste situazioni, UnrealScript fornisce le seguenti parole chiave:

Global: chiama la versione globale ( non di stato ) pi∙ derivata della funzione

Super: chiama la versione della funzione nella classe genitrice. La funzione chiamata pu≥ essere di stato o meno a seconda del contesto.

Super(classname): chiama la versione della funzione residente nella classe specificata. La funzione chiamata pu≥ essere di stato o meno a seconda del contesto.

Non Φ valido combinare specificatori di chiamata multipli ( es. Super(Actor).Global.Touch ). Ecco alcuni esempi di specificatori di chiamata:

class MyClass expands Pawn;
 
function MyExample( actor Other )
{
	Super(Pawn).Touch( Other );
	Global.Touch( Other );
	Super.Touch( Other );
}

Come esempio aggiuntivo, la funzione BeginPlay( ) viene chiamata quando un actor sta per entrare in gioco. La funzione BeginPlay( ) Φ implementata nella classe Actor e contiene alcune importanti funzionalitα che necessitano di essere eseguite. Adesso, si vuole ridefinire BeginPlay( ) nella propria classe MyClass per aggiungere alcune funzionalitα. Per farlo correttamente bisogna chiamare la versione di BeginPlay( ) nella classe genitrice:

class MyClass expands Pawn;
 
function BeginPlay()
{
	// Chiama la funzione di BeginPlay nella classe genitrice.
	Super.BeginPlay();
 
	// Modifica il contenuto di BeginPlay.
	//...
}

Valori di default delle variabili

Accedere ai valori di default delle variabili

UnrealEd permette ai level designers di modificare le variabili di default della classe di un oggetto. Quando un nuovo actor viene creato dalla classe, tutte le sue variabili sono impostate al valore di default. Alcune volte Φ utile reimpostare manualmente una variabile al suo valore di default. Per esempio, quando un giocatore lascia un oggetto dell’inventario, il codice dell’inventario necessita di riportare alcuni valori dell’actor al loro default. In UnrealScript si pu≥ accedere alle variabili di default di una classe con la parola chiave "Default". Per esempio:

var() float Health, Stamina;
//...
 
// Riporta alcune variabili al loro valore di default.
function ResetToDefaults()
{
	// Riporta health e stamina.
	Health = Default.Health;
	Stamina = Default.Stamina;
}

Accedere ai valori di default delle variabili in una variabile classe

Se si ha un riferimento ad una classe ( una variabile "class" oppure il tipo "class<classlimitor>" ) si pu≥ accedere alle proprietα di default della classe riferita senza avere un oggetto di quella classe. Esempio:

var class C;
var class<Pawn> PC;
// Accede al valore di default di LightBrightness nella classe Spotlight.
Health = class'Spotlight'.default.LightBrightness;
// Accede al valore di default di Health in una variabile classe identificata da PC. 
Health = PC.default.Health; 
// Accede al valore di default di Health con un casting di classe. 
Health = class<Pawn>(C).default.Health; 

Accedere a funzioni static in una variabile classe

Le funzioni static in una variabile classe vengono chiamate con questa sintassi:

var class C;
var class<Pawn> PC;
// Chiama una funzione static in una specifica classe
class'SkaarjTrooper'.static.SomeFunction(); 
// Chiama una funzione static in una variabile classe PC
PC.static.SomeFunction();
// Chiama una funzione static con un casting di classe
class<Pawn>(C).static.SomeFunction();

questioni tecniche avanzate

La compatibilitα binaria in UnrealScript

UnrealScript Φ progettato in maniera tale che le classi in file pacchetto possano evolvere nel tempo senza perdere compatibilitα binaria. Compatibilitα binaria significa "file binari dipendenti possono essere caricati e collegati senza errori".

I tipi di modifiche che possono essere compiute con sicurezza sono le seguenti:

I file script .uc in un pacchetto possono essere ricompilati senza perdere la compatibilitα.

Aggiungere nuove classi ad un pacchetto

Aggiungere nuove funzioni ad un pacchetto

Aggiungere nuovi stati ad un pacchetto

Aggiungere nuove variabili ad un pacchetto

Rimuovere variabili private da una classe

Altre trasformazioni sono in generale incerte, tra cui:

Aggiungere nuovi membri ad una struttura

Rimuovere una classe da un pacchetto

Cambiare il tipo di una variabile, il parametro di una funzione o il valore ritornato.

Cambiare il numero di parametri in una funzione

Note tecniche

Raccolta dei rifiuti ( garbage collection ): tutti gli oggetti ed actors in UT sono raccolti in un garbage collector. Questo collector utilizza la funzionalitα di serializzazione della classe UObject per rilevare ricorsivamente quali altri oggetti sono riferiti ad ogni oggetto attivo. Come risultato, un oggetto non deve essere esplicitamente distrutto, poichΘ il garbage collector lo trova appena diventa dereferenziato. Questo procedimento ha l’effetto collaterale che gli oggetti dereferenziati vengono distrutti dopo un certo tempo; comunque Φ un metodo molto pi∙ efficiente che un contatore di riferimento nel caso di distruzioni poco frequenti.

L’integrazione COM di Unreal: la classe di base UObject deriva da IUnknown in maniera da rendere UT interoperabile con il Component Object Model che richiede cambiamenti binari agli oggetti. Comunque UT non implementa ancora il COM al momento ed i benefici dell’integrazione non sono ancora chiari, quindi il progetto Φ tuttora in sospeso.

UnrealScript Φ basato su bytecode: il suo codice Φ compilato in una serie di bytecodes simili al p-code a ai bytecodes Java. Questo rende UnrealScript indipendente dalla piattaforma; pu≥ essere trasportato su altre piattaforme quali Macintosh o Unix e le varie versioni possono interoperare eseguendo gli stessi script.

Unreal Virtual Machine: l’engine di UT pu≥ essere considerato come una macchina virtuale per i giochi 3D quanto lo sono il linguaggio Java e la gerarchia di classi Java nei confronti degli script sulle pagine Web. L’Unreal Virtual Machine Φ portabile ed espandibile.

Il compilatore UnrealScript procede in due passaggi distinti, a differenza del C++: nel primo passaggio variabili, stati e funzioni sono memorizzate. Nel secondo passaggio lo script viene compilato in bytecodes.

Stato persistente di un actor: Φ importante notare che in Unreal, poichΘ l’utente pu≥ salvare il gioco in ogni momento, lo stato di tutti gli actors, incluso lo stato di esecuzione dei loro script, deve poter essere salvato. La richiesta di persistenza Φ il motivo che sta dietro alla limitazione di funzioni latenti chiamabili solo da codice di stato: questo codice esegue al pi∙ basso livello di stack e pu≥ cos∞ essere facilmente serializzato. Il codice di una funzione invece pu≥ esistere a qualsiasi livello di stack e pu≥ avere ( per esempio ) funzioni intrinsic C++ sotto di lei nello stack, il che non Φ di certo una situazione che pu≥ essere salvata su disco e poi ricaricata.

Unrealfiles: gli Unrealfiles sono i file nel formato binario nativo di Unreal. Gli Unrealfiles contengono un indice, insieme serializzato degli oggetti in quel particolare pacchetto. Gli Unrealfiles sono simili a DLL nel senso che possono contenere riferimenti ad altri oggetti contenuti in altri Unrealfiles. Questo approccio rende possibile distribuire il contenuto di UT in pacchetti predefiniti su Internet, in modo da ridurre i rischi e la lunghezza di download dividendo il contenuto.

PerchΘ UnrealScript non supporta variabili static: mentre C++ supporta variabili statiche per buone ragioni a causa delle radici di basso livello del linguaggio, e Java supporta variabili statiche per ragioni non ben chiare, queste variabili non sono presenti in UnrealScript a causa delle ambiguitα che si possono creare a causa degli ambiti in una struttura fortemente derivata. In UnrealScript Φ stato aggirato il problema non definendo le variabili statiche come parte del linguaggio e lasciando ai programmatori la libertα di creare variabili tipo static e global.

Strategia di programmazione in UnrealScript

Ecco alcuni punti da tenere in considerazione per scrivere efficace codice UnrealScript e trarre vantaggio dalla potenza del linguaggio.

UnrealScript Φ un linguaggio lento in confronto al C/C++. Un tipico programma C++ esegue circa 50 milioni di istruzioni al secondo mentre UnrealScript gira a circa 2,5 milioni, 20 volte di meno. Per≥ la filosofia di programmazione dietro a tutti gli script Φ questa: scrivere script che siano quasi sempre inattivi. In altre parole, UnrealScript va usato solo per gestire gli eventi "interessanti" che si vuole poi modificare, e non routine come la rotazione o i movimenti di base. Per esempio, quando si scrive lo script di un proiettile, si scrive normalmente una funzione HitWall( ), Bounce( ) e Touch( ) che descrivano il comportamento di fronte a determinati eventi. Cos∞ il 95% delle volte lo script del proiettile non sta eseguendo nulla e sta solo attendendo che gli venga notificato un evento. Questo metodo Φ assai efficiente. In un tipico livello, anche se UnrealScript Φ molto pi∙ lento del C++, l’esecuzione dell’UnrealScript occupa il 5-10% del tempo della CPU.

Utilizzare funzioni latenti ( come FinishAnim e Sleep ) il pi∙ possibile. Basando il flusso di esecuzione del proprio script su queste, si pu≥ creare codice orientato al tempo o alle animazioni, il che Φ molto efficiente in UnrealScript.

Fare attenzione al log di UT mentre si testa i propri script. Il runtime dell’UnrealScript genera nel log avvertimenti utili in merito ad errori minori.

Prestare molta attenzione a codice che pu≥ portare a ricorsivitα infinita. Per esempio, il comando "Move" muove l’actor e chiama la funzione Bump( ) se si colpisce qualcosa. Perci≥, se si usa il comando Move in una funzione Bump, si corre il rischio di cadere in ricorsivitα.

Creare e distruggere actors sono operazioni molto costose dalla parte del server ed ancora di pi∙ nel gioco di rete, poichΘ creazioni e distruzioni vanno ad occupare la bandwidth della rete. Vanno usate in maniera ragionevole. Per esempio, non provare a creare un sistema di particelle introducendo 100 actors distinti e mandandoli ovunque con diverse traiettorie, poichΘ sarebbe tremendamente lento.

Sfruttare il pi∙ possibile le capacitα di programmazione orientata agli oggetti dell’UnrealScript. Creare nuove funzionalitα ridefinendo funzioni e stati porta a codice pulito e semplice da modificare o integrare col lavoro di altre persone. E’ meglio evitare si utilizzare tecniche C tradizionale, come gli switch basati sulla classe di un actor, perchΘ codice come questo Φ scomodo quando si vogliono aggiungere nuove classi e modificare quelle esistenti.